﻿using System;
using System.Collections.Generic;
using System.Collections;
using System.Text;

namespace VGS {
    /// <summary>
    /// Object used when the score has changed
    /// </summary>
    public class ScoreChangedEventArgs : EventArgs
    {
        public readonly TeamEnum? Team;
        public readonly string Score;
        public readonly int TotalPoints;

        public ScoreChangedEventArgs(TeamEnum? team, string score, int total)
        {
            this.Team = team;
            this.Score = score;
            this.TotalPoints = total;
        }
    }


    /// <summary>
    /// This class contains a list of all the points for one set. The class
    /// is enumerable which means one can enumerate over each SetPoint.
    /// </summary>
    public class SetPoints : IList<SetPoint> {
        private List<SetPoint> points = new List<SetPoint>();
        private int homeTeamPoints = 0;
        private int awayTeamPoints = 0;
        private int homeTeamRots = 0; // keeps track of the total times rotations needed be done
        private int awayTeamRots = 0;
        private TeamEnum firstServe; // Team that started serving

        public EventHandler<ScoreChangedEventArgs> ScoreChanged;

        #region Constructor
        /// <summary>
        /// The constructor takes one argument - the team that starts serving
        /// </summary>
        /// <param name="starts_serving">Team that starts serve</param>
        public SetPoints(TeamEnum starts_serving) {
            firstServe = starts_serving;
        }
        #endregion


        #region Properties
        /// <summary>
        /// Gets the home team's score
        /// </summary>
        public int HomeTeamScore
        {
            get
            {
                return homeTeamPoints;
            }
        }


        /// <summary>
        /// Gets the away team's score
        /// </summary>
        public int AwayTeamScore
        {
            get
            {
                return awayTeamPoints;
            }
        }


        /// <summary>
        /// Gets the total number of rotations for the home team
        /// </summary>
        public int HomeTeamRotations
        {
            get
            {
                return homeTeamRots;
            }
        }


        /// <summary>
        /// Gets the total number of rotations for the away team
        /// </summary>
        public int AwayTeamRotations
        {
            get
            {
                return awayTeamRots;
            }
        }
       

        /// <summary>
        /// Gets the team that started serving
        /// </summary>
        public TeamEnum FirstServe
        {
            get
            {
                return firstServe;
            }
        }


        /// <summary>
        /// Returns the team that has the serve
        /// </summary>
        public TeamEnum? HasServe
        {
            get
            {
                if (points.Count == 0)
                    return FirstServe;
                else
                    return points[points.Count - 1].Team;
            }
        }
        #endregion

        #region IList<> methods
        /// <summary>
        /// Returns the index of point
        /// </summary>
        /// <param name="point">SetPoint to look for</param>
        /// <returns>Zero-based index of point. -1 if not found.</returns>
        public int IndexOf(SetPoint point)
        {
            return points.IndexOf(point);
        }

        /// <summary>
        /// Inserts a SetPoint at the specified index
        /// </summary>
        /// <param name="index">Position to insert at</param>
        /// <param name="item">SetPoint to insert</param>
        /// <exception cref="ArgumentOutOfRangeException">Index out of range</exception>
        public void Insert(int index, SetPoint item)
        {
            if (index < 0 || index >= points.Count)
                throw new ArgumentOutOfRangeException("index", "Index out of range");
            points.Insert(index, item);
        }

        /// <summary>
        /// Removes the point at the specified index
        /// </summary>
        /// <param name="pos"></param>
        /// <exception cref="ArgumentOutOfRangeException">index must be between 0 and Count-1</exception>
        public void RemoveAt(int index)
        {
            if (index < 0 || index >= points.Count)
                throw new ArgumentOutOfRangeException("index", "Index must be between 0 and Count-1");
            points.RemoveAt(index);
        }

        /// <summary>
        /// Gets the set point based on its zero-based index
        /// </summary>
        /// <param name="index">Zero-based index</param>
        /// <returns>SetPoint at "index"</returns>
        public SetPoint this[int index]
        {
            get
            {
                if (index < 0 || index >= points.Count)
                    throw new ArgumentOutOfRangeException("index", "Index out of range");
                return points[index];
            }
            set
            {
                if (index < 0 || index >= points.Count)
                    throw new ArgumentOutOfRangeException("index", "Index out of range");
                points[index] = value;
            }
        }
        #endregion

        #region ICollection<> methods
        /// <summary>
        /// Gets the number of elements contained in the list.
        /// </summary>
        public int Count
        {
            get
            {
                return points.Count;
            }
        }

        /// <summary>
        /// Removes all elements.
        /// </summary>
        public void Clear()
        {
            points.Clear();
        }

        /// <summary>
        /// Checks whether or not the specified item exists.
        /// </summary>
        /// <param name="item">The SetPoint item</param>
        /// <returns>true if the item exists, otherwise false</returns>
        public bool Contains(SetPoint item)
        {
            return points.Contains(item);
        }

        /// <summary>
        /// Copies the list to the specified array
        /// </summary>
        /// <param name="array">Array to copy to</param>
        /// <param name="arrayIndex">Zero-based starting index to copy from</param>
        /// <exception cref="ArgumentNullException"></exception>
        /// <exception cref="ArgumentOutOfRangeException"></exception>
        /// <exception cref="ArgumentException"></exception>
        public void CopyTo(SetPoint[] array, int arrayIndex)
        {
            try
            {
                points.CopyTo(array, arrayIndex);
            }
            catch (Exception)
            {
                throw; // Rethrow same exception
            }
        }

        /// <summary>
        /// Adds the new item to the list.
        /// </summary>
        /// <param name="item">SetPoint item to add</param>
        public void Add(SetPoint item)
        {
            // Make sure one of the teams scored, otherwise return.
            if (item.Team == null)
                return;

            VolleyMatch vm = VolleyMatch.Instance;
            
            // Home team scored
            if (item.Team == TeamEnum.HOME_TEAM)
            {
                homeTeamPoints++;

                // Check if we should rotate
                if (points.Count == 0)
                {
                    if (item.Team != FirstServe)
                        vm.Sets.CurrentSet.Lineups.HomeTeam.Rotate();
                }
                else
                {
                    if (item.Team != points[points.Count - 1].Team)
                        vm.Sets.CurrentSet.Lineups.HomeTeam.Rotate();
                }

                // If a specified player scored
                if (item.Player != null)
                {
                    // Make sure the player is added to the statistics dictionary
                    if (!item.Player.Stats.ContainsKey(vm.Sets.CurrentSet.Number))
                    {
                        item.Player.Stats[vm.Sets.CurrentSet.Number] = new PlayerStatistics();
                    }

                    // Add the point to the log
                    switch (item.Type)
                    {
                        case PointType.POINT_ATTACK:
                            Log.Instance.AddMessage(string.Format("{2}'s {0} ({1}) attacked and scored",
                                item.Player.Name, item.Player.Shirt, vm.HomeTeam.Name));
                            item.Player.Stats[vm.Sets.CurrentSet.Number].AddAttackPoint();
                            break;
                        case PointType.POINT_DEFENSE:
                            Log.Instance.AddMessage(string.Format("{2}'s {0} ({1}) blocked and scored",
                                item.Player.Name, item.Player.Shirt, vm.HomeTeam.Name));
                            item.Player.Stats[vm.Sets.CurrentSet.Number].AddDefensePoint();
                            break;
                        case PointType.POINT_SERVE:
                            Log.Instance.AddMessage(string.Format("{2}'s {0} ({1}) served and scored",
                                item.Player.Name, item.Player.Shirt, vm.HomeTeam.Name));
                            item.Player.Stats[vm.Sets.CurrentSet.Number].AddServePoint();
                            break;
                    }
                }
                else
                    // Add the point to the log
                    Log.Instance.AddMessage(string.Format("{0} scored due to opponent mistake",
                        vm.HomeTeam.Name));
            }

            // Away team scored
            else if (item.Team == TeamEnum.AWAY_TEAM)
            {
                awayTeamPoints++;

                // Check if we should rotate
                if (points.Count == 0)
                {
                    if (item.Team != FirstServe)
                        vm.Sets.CurrentSet.Lineups.AwayTeam.Rotate();
                }
                else
                {
                    if (item.Team != points[points.Count - 1].Team)
                        vm.Sets.CurrentSet.Lineups.AwayTeam.Rotate();
                }

                if (item.Player != null)
                {
                    // Make sure the player is added to the statistics dictionary
                    if (!item.Player.Stats.ContainsKey(vm.Sets.CurrentSet.Number))
                    {
                        item.Player.Stats[vm.Sets.CurrentSet.Number] = new PlayerStatistics();
                    }

                    // Add the point to the log
                    switch (item.Type)
                    {
                        case PointType.POINT_ATTACK:
                            Log.Instance.AddMessage(string.Format("{2}'s {0} ({1}) attacked and scored",
                                item.Player.Name, item.Player.Shirt, vm.AwayTeam.Name));
                            item.Player.Stats[vm.Sets.CurrentSet.Number].AddAttackPoint();
                            break;
                        case PointType.POINT_DEFENSE:
                            Log.Instance.AddMessage(string.Format("{2}'s {0} ({1}) blocked the attacker",
                                item.Player.Name, item.Player.Shirt, vm.AwayTeam.Name));
                            item.Player.Stats[vm.Sets.CurrentSet.Number].AddDefensePoint();
                            break;
                        case PointType.POINT_SERVE:
                            Log.Instance.AddMessage(string.Format("{2}'s {0} ({1}) served and scored",
                                item.Player.Name, item.Player.Shirt, vm.AwayTeam.Name));
                            item.Player.Stats[vm.Sets.CurrentSet.Number].AddServePoint();
                            break;
                    }
                }
                else
                    // Add the point to the log
                    Log.Instance.AddMessage(string.Format("{0} scored due to opponent mistake",
                        vm.AwayTeam.Name));
            }

            // Add the point and trigger event
            points.Add(item);
            OnScoreChanged(new ScoreChangedEventArgs(item.Team, ToString(), points.Count));
        }

        /// <summary>
        /// Removes the first occurance of the specified item.
        /// </summary>
        /// <param name="item">SetPoint item to remove</param>
        /// <returns>true if the item was removed, otherwise false</returns>
        public bool Remove(SetPoint item)
        {
            return points.Remove(item);
        }

        /// <summary>
        /// Gets whether the list is readonly or not. Since this one isn't, false
        /// is hardcoded.
        /// </summary>
        public bool IsReadOnly
        {
            get
            {
                return false;
            }
        }
        #endregion

        #region IEnumerable<> methods
        /// <summary>
        /// Make it possible to enumerate over the set points
        /// </summary>
        /// <returns>Each SetPoint</returns>
        public IEnumerator<SetPoint> GetEnumerator()
        {
            foreach (SetPoint s in points)
                yield return s;
        }
        #endregion

        #region IEnumerable methods
        /// <summary>
        /// Explicit implementation to make this hidden.
        /// Returns the generic enumerator.
        /// </summary>
        /// <returns>Each SetPoint</returns>
        IEnumerator IEnumerable.GetEnumerator()
        {
            return GetEnumerator();
        }
        #endregion

        #region Methods
        /// <summary>
        /// Triggers the event
        /// </summary>
        /// <param name="e">ScoreChangedEventArgs</param>
        protected virtual void OnScoreChanged(ScoreChangedEventArgs e)
        {
            if (ScoreChanged != null) ScoreChanged(this, e);
        }
        
        /// <summary>
        /// Undo the last score
        /// </summary>
        /// <returns>True on successful undo, false otherwise (no score to undo)</returns>
        public bool Undo() {
            if (this.Count > 0) {
                VolleyMatch vm = VolleyMatch.Instance;
                SetPoint point = points[points.Count - 1];

                // Remove the point
                if (point.Team == TeamEnum.HOME_TEAM)
                {
                    homeTeamPoints--;
                    
                    // Remove the statistic for the point
                    if (point.Player != null)
                    {
                        switch (point.Type)
                        {
                            case PointType.POINT_ATTACK:
                                point.Player.Stats[vm.Sets.CurrentSet.Number].RemoveAttackPoint();
                                //homeTeamPlayerStats[point.Player].RemoveAttackPoint();
                                break;
                            case PointType.POINT_DEFENSE:
                                point.Player.Stats[vm.Sets.CurrentSet.Number].RemoveDefensePoint();
                                //homeTeamPlayerStats[point.Player].RemoveDefensePoint();
                                break;
                            case PointType.POINT_SERVE:
                                point.Player.Stats[vm.Sets.CurrentSet.Number].RemoveServePoint();
                                //homeTeamPlayerStats[point.Player].RemoveServePoint();
                                break;
                        }
                    }
                }
                else if (point.Team == TeamEnum.AWAY_TEAM)
                {
                    awayTeamPoints--;

                    // Remove the statistic for the point
                    if (point.Player != null)
                    {
                        switch (point.Type)
                        {
                            case PointType.POINT_ATTACK:
                                point.Player.Stats[vm.Sets.CurrentSet.Number].RemoveAttackPoint();
                                //awayTeamPlayerStats[point.Player].RemoveAttackPoint();
                                break;
                            case PointType.POINT_DEFENSE:
                                point.Player.Stats[vm.Sets.CurrentSet.Number].RemoveDefensePoint();
                                //awayTeamPlayerStats[point.Player].RemoveDefensePoint();
                                break;
                            case PointType.POINT_SERVE:
                                point.Player.Stats[vm.Sets.CurrentSet.Number].RemoveServePoint();
                                //awayTeamPlayerStats[point.Player].RemoveServePoint();
                                break;
                        }
                    }
                }

                // Remove the point
                points.Remove(point);
                Log.Instance.AddMessage("The last point was removed");
                if (points.Count == 0)
                    OnScoreChanged(new ScoreChangedEventArgs(FirstServe, ToString(), points.Count));
                else
                    OnScoreChanged(new ScoreChangedEventArgs(points[points.Count-1].Team, ToString(), points.Count));

                // Last point successfully removed
                return true;
            }
            return false;
        }
        #endregion

        #region Overridden methods
        public override string ToString() {
            return string.Format("{0} - {1}", HomeTeamScore, AwayTeamScore);
            //return base.ToString();
        }
        #endregion
    }
}
